package com.cropdoctor.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cropdoctor.common.CustomException;
import com.cropdoctor.common.EsMsgHandler;
import com.cropdoctor.common.Result;
import com.cropdoctor.mapper.CommentMapper;
import com.cropdoctor.mapper.PostMapper;
import com.cropdoctor.model.constants.PageParams;
import com.cropdoctor.model.constants.PageResult;
import com.cropdoctor.model.po.CommentPo;
import com.cropdoctor.model.po.PostPo;
import com.cropdoctor.model.vo.PostVO;
import com.cropdoctor.service.PostService;
import com.cropdoctor.util.AliOssUtil;
import com.cropdoctor.util.ContextGet;

import io.redisearch.Document;
import io.redisearch.Query;
import io.redisearch.SearchResult;
import io.redisearch.client.Client;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Service
public class PostServiceImpl implements PostService {
    @Autowired
    PostMapper postMapper;
    @Autowired
    AliOssUtil ossUtil;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    @Qualifier("postSearchClient")
    Client redisSearchClient;

    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    CommentMapper commentMapper;

    final static String POST_KEY = "post";
    final static String POST_LIKE_KEY = "post_like_";
    @Override
    @Transactional
    public Result<String> createPost(PostPo postPo,MultipartFile img) {

        String filename = img.getOriginalFilename();
        File tempFile = null;
        byte[] bytes = null;
        try {
            tempFile = File.createTempFile("oss", ".temp");
            String absolutePath = tempFile.getAbsolutePath();//临时文件路径
            bytes = img.getBytes();
            img.transferTo(tempFile);
            String url = null;
            //获取原始的文件名
            String originalFilename = tempFile.getName();
            //在oss中存储名字就是UUID + 文件的后缀名
            String objectName = UUID.randomUUID() + originalFilename.substring(originalFilename.lastIndexOf("."));
            url = ossUtil.upload(bytes, "post/"+objectName);
            postPo.setUrl(url);
        } catch (IOException e) {
            e.printStackTrace();
            throw new CustomException("创建临时文件失败!");
        }finally {
            if(tempFile != null) tempFile.delete();//程序退出时删除临时文件
        }

        //将数据插入到帖子表中
        postPo.setCreateDateTime(LocalDateTime.now());
        int result1 = postMapper.insert(postPo);

        //若插入失败，则抛出CustomException异常
        if (result1 <= 0){
            throw new CustomException("帖子信息插入失败！");
        }
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",Long.valueOf(postPo.getId()).toString());//放入对应主键
        map.put("Po", JSON.toJSONString(postPo));
        map.put("type","1");//新增操作
        map.put("userId",ContextGet.getUserId());
        map.put("sqlTable","post_table");//告诉错误消息出问题的是哪一个表中的主键

        PostVO postVO = new PostVO();
        BeanUtils.copyProperties(postPo,postVO);
        addToRefreshPostCache(postVO);
        rabbitTemplate.convertAndSend(EsMsgHandler.EXCHANGE_TOPIC,EsMsgHandler.ROUTINGKEY_POST,map);
        return Result.success("插入成功！");
    }

    @Override
    @Transactional
    public Result<String> userDeletePost(long idPost) {
        //获取目标帖子分布者的id
        PostPo postPo = postMapper.selectById(idPost);
        long userId = postPo.getUserId();
        if (ContextGet.getUserId() != userId) throw new CustomException("只能删除自己的帖子！");
        //通过主键id，将相应数据从帖子表中删除
        int result1 = postMapper.deleteById(idPost);
        //相关联的评论数据一并删除
        commentMapper.delete(new LambdaQueryWrapper<CommentPo>().eq(CommentPo::getPostId,idPost));
        //若删除失败，则抛出CustomException异常
        if (result1 <= 0){
            throw new CustomException("帖子信息删除失败！");
        }

        //告知es引擎需要删除该数据
        HashMap<String, Object> map = new HashMap<>();
        map.put("id",idPost);
        map.put("type","0");
        map.put("sqlTable","post_table");//告诉错误消息出问题的是哪一个表中的主键
        map.put("userId",ContextGet.getUserId());
        rabbitTemplate.convertAndSend(EsMsgHandler.EXCHANGE_TOPIC,EsMsgHandler.ROUTINGKEY_POST,map);

        refreshPostCache();
        return Result.success("删除成功！");
    }

    @Override
    public Result<List<PostPo>> getUserAllPost() {
        //构造查询条件
        LambdaQueryWrapper<PostPo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PostPo::getUserId, ContextGet.getUserId());
        //通过用户id，从帖子表中查询所有该用户发布的帖子信息
        List<PostPo> postPos = postMapper.selectList(wrapper);
        return Result.success(postPos);
    }

    @Override
    public Result<PageResult<PostVO>> getAllPost(PageParams pageParams,String keyword) {
        if(!"".equals(keyword)){
            StringBuilder q = new StringBuilder();
            q.append(keyword + " ");
            Query query = new Query(q.toString());

            int offset = (int) ((pageParams.getPageNo() - 1) * pageParams.getPageSize());
            int num = pageParams.getPageSize().intValue();
            query.limit(offset, num);

            SearchResult search = redisSearchClient.search(query);
            List<Document> docs = search.docs;
            List<PostVO> voList = new ArrayList<>();
            for (Document doc : docs) {
                DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss");
                LocalDateTime dateTime = LocalDateTime.parse(doc.getString("create_date_time"), df);
                PostVO postVO = PostVO.builder()
                        .id(Long.valueOf(doc.getString("id")))
                        .title(doc.getString("title"))
                        .url(doc.getString("url"))
                        .createDateTime(dateTime)
                        .build();
                voList.add(postVO);
            }
            return Result.success(new PageResult<PostVO>(voList,search.totalResults,pageParams.getPageNo(),pageParams.getPageSize()));
        }else{
            long start = (pageParams.getPageNo() - 1) * pageParams.getPageSize();//起始位置
            Long total = redisTemplate.opsForList().size(POST_KEY); //此处获得是所有的数据
            if(total == 0) {//缓存中无数据 从数据库中更新到缓存上
                List<PostVO> list = postMapper.selectToVO();
                total = Long.valueOf(list.size());//数据总量
                if(list != null && list.size() != 0) redisTemplate.opsForList().rightPushAll(POST_KEY,list);//存入缓存
            }
            //封装返回结果类
            long end = total > start + pageParams.getPageSize() ? start + pageParams.getPageSize() - 1 : total - 1;
            List<PostVO> voList = redisTemplate.opsForList().range(POST_KEY, start, end);

            return Result.success(new PageResult<PostVO>(voList,total,pageParams.getPageNo(),pageParams.getPageSize()));
        }
    }

    @Override
    public Result<String> likePost(long idPost) {
        Long userId = ContextGet.getUserId();
        String key = POST_LIKE_KEY+idPost;
        Boolean isMember = redisTemplate.opsForSet().isMember(key, userId.toString());
        if(Boolean.FALSE.equals(isMember)){
            //点赞数+1
            int update = postMapper.increaseLikeNumber(idPost);
            if(update > 0){
                redisTemplate.opsForSet().add(key,userId.toString());//保存到set集合中
                return Result.success("1");//点赞
            }
        }else{
            //如果已经点赞则取消点赞
            int update = postMapper.decreaseLikeNumber(idPost);
            if(update > 0){
                redisTemplate.opsForSet().remove(key,userId.toString());//从set集合中移除
                return Result.success("-1");//取消点赞
            }
        }
        return Result.error("失败!");
    }

    @Override
    public Result<String> adminDeletePost(long idPost) {
        //通过主键id，将相应数据从帖子表中删除
        int result1 = postMapper.deleteById(idPost);
        //若删除失败，则抛出CustomException异常
        if (result1 <= 0){
            throw new CustomException("帖子信息删除失败！");
        }
        else return Result.success("删除成功！");
    }

    public void refreshPostCache(){
        redisTemplate.delete(POST_KEY);
        List<PostVO> voList = postMapper.selectToVO();
        if(voList != null && voList.size() != 0) redisTemplate.opsForList().rightPushAll(POST_KEY,voList);
    }

    //新增缓存
    public void addToRefreshPostCache(PostVO postVO){
        redisTemplate.opsForList().rightPush(POST_KEY,postVO);
    }
}
